/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/dx-window-impl.hh"
#include "carina/dx-renderer.hh"

namespace Carina
{
string GetLastErrorString()
{
	auto err = ::GetLastError();
	auto msg = CREATE_SCOPED(LPSTR, LocalFree);
    msg = nullptr;
    DWORD res = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                              FORMAT_MESSAGE_ALLOCATE_BUFFER |
                              FORMAT_MESSAGE_IGNORE_INSERTS,  
                              nullptr,
                              err,
                              MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                              (LPTSTR)&msg,
                              0,
                              nullptr);
    if(res && msg)
        return static_cast<char*>(msg);
    else
    {
        std::stringstream ss;
        ss << "Generic error: 0x" << std::hex << err;
        return ss.str();
    }
}

DXWindowImpl::DXWindowImpl()
{
}

void DXWindowImpl::create(WindowManager& wmgr, size_t w, size_t h, size_t samples, const WindowInfo& parent=WindowInfo())
{
    DWORD dwStyle;
    if(parent.getWindowId())
        dwStyle = WS_CHILD;
    else
        dwStyle = WS_OVERLAPPEDWINDOW | WS_SYSMENU;

    m_MSAA = samples;
    m_Window = CreateWindowEx(0, "CarinaWindow", "Untitled window", dwStyle,
                              0, 0, w, h, parent.getWindowId(), 0, (HINSTANCE)GetModuleHandle(nullptr), this);
    if(!m_Window)
        THROW_EXCEPTION("has failed to create window");

    m_DC = GetDC(m_Window);

    DXGI_SWAP_CHAIN_DESC sd;
    sd.BufferDesc.Width = w;
    sd.BufferDesc.Height = h;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    sd.SampleDesc.Count = samples;
    sd.SampleDesc.Quality = -(samples > 1) & D3D11_STANDARD_MULTISAMPLE_PATTERN;

    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.BufferCount = 1;
    sd.OutputWindow = m_Window;
    sd.Windowed = true;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    sd.Flags = 0;

#ifndef NDEBUG
    const UINT flags = D3D11_CREATE_DEVICE_DEBUG;
#else
    const UINT flags = 0;
#endif

    D3D_FEATURE_LEVEL feature_level;
    HR_ASSERT(D3D11CreateDeviceAndSwapChain_MS(0,
                    D3D_DRIVER_TYPE_HARDWARE,
                    0,
                    flags,
                    0,
                    0,
                    D3D11_SDK_VERSION,
                    &sd,
                    &m_SwapChain,
                    &m_Device,
                    &feature_level,
                    &m_DeviceContext));

    ComRAII<ID3D11Texture2D> back_buffer;
    HR_ASSERT(m_SwapChain->GetBuffer(0, UUIDOF(ID3D11Texture2D), reinterpret_cast<void**>(&back_buffer)));
    HR_ASSERT(m_Device->CreateRenderTargetView(back_buffer, (D3D11_RENDER_TARGET_VIEW_DESC*)0, &m_RenderTargetView));

    D3D11_TEXTURE2D_DESC depthBufDesc;
    depthBufDesc.Width = w;
    depthBufDesc.Height = h;
    depthBufDesc.MipLevels = 1;
    depthBufDesc.ArraySize = 1;
    depthBufDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthBufDesc.SampleDesc.Count = samples;
    depthBufDesc.SampleDesc.Quality = -(samples > 1) & D3D11_STANDARD_MULTISAMPLE_PATTERN;
    depthBufDesc.Usage = D3D11_USAGE_DEFAULT;
    depthBufDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthBufDesc.CPUAccessFlags = 0;
    depthBufDesc.MiscFlags = 0;

	HR_ASSERT(m_Device->CreateTexture2D(&depthBufDesc, nullptr, &m_DepthStencilBuf));
	HR_ASSERT(m_Device->CreateDepthStencilView(m_DepthStencilBuf, nullptr, &m_DepthStencilView));
    m_DeviceContext->OMSetRenderTargets(1, &m_RenderTargetView, m_DepthStencilView);

    m_Renderer = make_aligned_shared<DXRenderer>(m_Device);
}

void DXWindowImpl::resize(size_t w, size_t h)
{
    // We ignore minimization.
    if(!w && !h)
        return;
    static_cast<DXRenderer&>(*m_Renderer)._releaseBuffers();

	m_DeviceContext->OMSetRenderTargets(0, nullptr, nullptr);

    m_RenderTargetView.release();
    m_DepthStencilView.release();
    m_DepthStencilBuf.release();

    ComRAII<ID3D11Texture2D> back_buffer;
    HR_ASSERT(m_SwapChain->ResizeBuffers(1, w, h, DXGI_FORMAT_R8G8B8A8_UNORM, 0));
    HR_ASSERT(m_SwapChain->GetBuffer(0, UUIDOF(ID3D11Texture2D), reinterpret_cast<void**>(&back_buffer)));
	HR_ASSERT(m_Device->CreateRenderTargetView(back_buffer, nullptr, &m_RenderTargetView));

    D3D11_TEXTURE2D_DESC depthBufDesc;
    depthBufDesc.Width = w;
    depthBufDesc.Height = h;
    depthBufDesc.MipLevels = 1;
    depthBufDesc.ArraySize = 1;
    depthBufDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthBufDesc.SampleDesc.Count = m_MSAA;
    depthBufDesc.SampleDesc.Quality = -(m_MSAA > 1) & D3D11_STANDARD_MULTISAMPLE_PATTERN;
    depthBufDesc.Usage = D3D11_USAGE_DEFAULT;
    depthBufDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthBufDesc.CPUAccessFlags = 0;
    depthBufDesc.MiscFlags = 0;

	HR_ASSERT(m_Device->CreateTexture2D(&depthBufDesc, nullptr, &m_DepthStencilBuf));
	HR_ASSERT(m_Device->CreateDepthStencilView(m_DepthStencilBuf, nullptr, &m_DepthStencilView));
    m_DeviceContext->OMSetRenderTargets(1, &m_RenderTargetView, m_DepthStencilView);

    static_cast<DXRenderer&>(*m_Renderer)._updateBuffers();
}

void DXWindowImpl::destroy()
{
    ReleaseDC(m_Window, m_DC);
}

void DXWindowImpl::swapBuffers()
{
    HR_ASSERT(m_SwapChain->Present(0,0));
}
}
